{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "view-in-github"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/hsgw/keyboard-made-by-python/blob/main/notebook/jp/pcb.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "> これは[キーボード #1 Advent Calendar 2022](https://adventar.org/calendars/7529)の20日目の記事の一部です。   \n",
        "> トップページは[Pythonだけでキーボードを作る](https://5z6p.com/2022/12/21/ac2022/)です。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "g6rr65y7TUEE"
      },
      "source": [
        "# 基板の設計\n",
        "\n",
        "<img src=\"../imgs/kbd_python_preview_top.png\" height=\"300px\">\n",
        "<img src=\"../imgs/kbd_python_preview_bot.png\" height=\"300px\">\n",
        "\n",
        "このノートブックではキーボードの基板を設計し、最終的に基板を生産するためのデータ`ガーバーファイル`を得ることを目標とします。\n",
        "\n",
        "通常の工程で基板を作るにはまず`回路図エディタ`で`回路図`を書いて使用する部品とその結線情報をまとめて`ネットリスト`として出力し、`基板エディタ`を用いて実際に`基板`へ部品を配置・配線します。"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "RwgtrhL6DhoQ"
      },
      "source": [
        "## 記事を読むための注意\n",
        "このノートブックには2回実行すると正常に動作しないセルが含まれています。\n",
        "\n",
        "それを防止するために上のメニューから`ランタイム>全てのセルを実行`してから読み始めると良いかもしれません。実行完了には少し時間がかかります。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Topn5GuhW6Z2"
      },
      "source": [
        "## リソースのダウンロード\n",
        "以下のセルを実行してkicadのライブラリや他のリソースが入ったリポジトリをクローンしておきます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "hEThVfBsW8RE",
        "outputId": "b4211474-89e8-4cf3-9779-fdbd6e895db2"
      },
      "outputs": [],
      "source": [
        "!git clone https://github.com/hsgw/keyboard-made-by-python/"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Ancu-CO6W_Y4"
      },
      "source": [
        "# skidlでネットリスト(回路図)を設計する\n",
        "Pythonを使った設計では回路図を書かずにコードで使用する部品と結線情報を定義してネットリストを出力します。\n",
        "\n",
        "[skidl](https://github.com/devbisme/skidl)というライブラリを用います。このライブラリは独自フォーマットの部品ライブラリだけでなく、kicadの部品ライブラリをインポートして使用します。また、ネットリストはkicadのPCBエディタと互換性があり直接kicadへ読み込んで回路図を書くこともできます。\n",
        "\n",
        "- skidl  https://github.com/devbisme/skidl\n",
        "- skidlのドキュメント  https://devbisme.github.io/skidl/"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-cW5lSiPYuUS"
      },
      "source": [
        "## skidlのインストールとインポート\n",
        "[skidl](https://github.com/devbisme/skidl)をインストールしてインポートします。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "Oy3vcMzUWq9g",
        "outputId": "aa8168c0-4184-4e8b-cd14-815035fc8882"
      },
      "outputs": [],
      "source": [
        "# インストールには時間がかかります\n",
        "!pip install skidl"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "KMF3_yPHTF0L",
        "outputId": "e51fa261-a270-4d10-c6a9-b27fae81e2b9"
      },
      "outputs": [],
      "source": [
        "# kicadのデフォルトパスが環境変数に設定されていないのでWarningが出ます\n",
        "from skidl import *"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LTeD6nJwogVT"
      },
      "source": [
        "## 定数を宣言する\n",
        "何度も出てくる値を定数として宣言しておきます。この値を使い回すことで変更に強い設計になります。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "6jwqmkIjo5MH"
      },
      "outputs": [],
      "source": [
        "KEY_COUNT = 10\n",
        "COL_COUNT = 4\n",
        "ROW_COUNT = 3\n",
        "MATRIX_MAP = [\n",
        "        (0,1),(0,2),(0,3),  \n",
        "        (1,1),(1,2),(1,3),\n",
        "  (2,0),(2,1),(2,2),(2,3)\n",
        "]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QWaPdvrNmmzk"
      },
      "source": [
        "## kicadライブラリの読み込みと部品の準備\n",
        "使用するkicadライブラリのパスを追加をします。\n",
        "\n",
        "kicadがインストールされている環境であれば通常のライブラリへのパスがデフォルトで追加されているはずですが、今回はkicadのインストールされていない環境で実行するために使う部品だけ別に用意しておきます。\n",
        "先程githubからcloneしてきた`keyboard-made-by-python/hardware/kicad_libs`に入っています。\n",
        "\n",
        "シンボルとフットプリントを結びつけて必要な個数だけ部品を用意します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "EjCwQBClnF5q",
        "outputId": "0c0ad665-d336-4d2a-af5e-55f8886532ac"
      },
      "outputs": [],
      "source": [
        "# 使用するライブラリを登録する\n",
        "# シンボル・フットプリントどちらも別で登録する\n",
        "lib_search_paths[KICAD].append(\"keyboard-made-by-python/hardware/kicad_libs\")\n",
        "footprint_search_paths[KICAD].append(\"keyboard-made-by-python/hardware/kicad_libs/kicad.pretty\")\n",
        "\n",
        "# 同じ部品を何回も使うならシンボルとフットプリントを結びつけたテンプレートとして読み込んでおく\n",
        "diode = Part(\n",
        "  \"kicad_symbols\", \"D_Small_ALT\", TEMPLATE, footprint=\"kicad:D_SOD123_hand\"\n",
        ")\n",
        "switch = Part(\n",
        "  \"kicad_symbols\",\n",
        "  \"SW_Push\",\n",
        "  TEMPLATE,\n",
        "  footprint=\"kicad:SW_Cherry_MX_1.00u_PCB\",\n",
        ")\n",
        "\n",
        "# 宣言した定数とテンプレートを使ってダイオードとスイッチを用意する\n",
        "# それぞれ KEY_COUNT 個用意して配列にいれておく\n",
        "diodes = diode(KEY_COUNT)\n",
        "switches = switch(KEY_COUNT)\n",
        "\n",
        "# ひとつしか使わない部品はそのまま読み込む\n",
        "xiao = Part(\"kicad_symbols\", \"xiao_rp2040\", footprint=\"kicad:xiao_rp2040\")\n",
        "oled = Part(\"kicad_symbols\", \"oled_i2c\", footprint=\"kicad:oled_i2c\")\n",
        "\n",
        "# printするとシンボルのPin情報が見えます\n",
        "print(diode, switch, xiao, oled)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "oG5xuNjqq6qg"
      },
      "source": [
        "## PinやNetを接続する\n",
        "用意した部品のピン同士を接続を入力して回路の構成を定義します。\n",
        "\n",
        "このときなるべく配線自体に`Net`名をつけるようにします。`Net`を基準に部品の`Pin`を繋いでいくことで同じところにつながるもの(電源やスイッチマトリクス)を整理して見やすいコードにします。また、kicadへネットリストをインポートしたときに表示されるので目印にもなります。\n",
        "\n",
        "今回の設計でも基板配線時に`ピン番号`を`Net`から読み出します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "PI9UK581tf5H",
        "outputId": "3cb4724e-8401-4655-a2e2-66fb5cb5368f"
      },
      "outputs": [],
      "source": [
        "# Pin情報を表示しながら配線すると便利\n",
        "print(xiao)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "Ufo-bwPNtWzt",
        "outputId": "833c4292-eef8-46e5-c648-298eb0fe0c53"
      },
      "outputs": [],
      "source": [
        "# スイッチマトリクスのROW,COLのネットリストが入った配列を作る\n",
        "netRows = [Net(f\"ROW{i}\") for i in range(ROW_COUNT)]\n",
        "netCols = [Net(f\"COL{i}\") for i in range(COL_COUNT)]\n",
        "\n",
        "\n",
        "# ROWのNet -> スイッチの1ピン  スイッチの2ピン -> ダイオードのカソード  ダイオードのアノード -> COLのNet をまとめて宣言する\n",
        "# NetやPinは`&`で繋ぐと接続される\n",
        "# 部品のPinには part[\"pin name\"]でアクセス出来る\n",
        "# 部品のPinの添字を2つにするとinとoutになる\n",
        "# 例: sw[\"1\"]につながるNetかPin & sw[\"1 2\"] & sw[\"2\"]に繋がるNetかPin\n",
        "for sw, d, mapping in zip(switches, diodes, MATRIX_MAP):\n",
        "  netRows[mapping[0]] & sw[\"1 2\"] & d[\"K A\"] & netCols[mapping[1]]\n",
        "\n",
        "# スイッチマトリクスとxiaoを接続する\n",
        "# NetにPinを`+`ことで接続する\n",
        "netCols[0] += xiao[8]\n",
        "netCols[1] += xiao[3]\n",
        "netCols[2] += xiao[4]\n",
        "netCols[3] += xiao[5]\n",
        "\n",
        "netRows[0] += xiao[1]\n",
        "netRows[1] += xiao[6]\n",
        "netRows[2] += xiao[7]\n",
        "\n",
        "# oledとxiaoも接続する\n",
        "# Net(\"3.3V\")みたいに直接Netを宣言して繋いでもOK\n",
        "Net(\"3.3V\") & oled[\"Vcc\"] & xiao[\"3.3V\"]\n",
        "Net(\"GND\") & oled[\"GND\"] & xiao[\"GND\"]\n",
        "Net(\"SDA\") & oled[\"SDA\"] & xiao[9]\n",
        "Net(\"SCL\") & oled[\"SCL\"] & xiao[11]\n",
        "\n",
        "# printすると接続されているPinが表示される\n",
        "print(netRows[0])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ygS8U7MywJ8k"
      },
      "source": [
        "## ERCとネットリストの出力\n",
        "ERC(Electrical Rule Check・回路図のルールチェック)をかけて、ネットリストを出力します。\n",
        "これで回路図・ネットリストは完成です！"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 330
        },
        "id": "7UCaNTPgwJVV",
        "outputId": "209f3e2a-ff1e-4806-d9b4-13bd61d0f927"
      },
      "outputs": [],
      "source": [
        "# 他のセルを複数回実行しているとエラーが出たり回路が複数個になったりするかもしれません\n",
        "# そのときは`メニュー`の`ランタイム -> ランタイムを再起動`して最初からやりなおすか`再起動してすべてのセルを実行`してください\n",
        "# 未結線のWarningが出ますが問題ありません\n",
        "ERC()\n",
        "generate_netlist(file_=\"keyboard.net\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5URJ6fd7xzse"
      },
      "source": [
        "出力されたネットリストは今記事では使用しませんが、kicadのpcbnewのネットリスト読み込みからインポートしてそのまま基板を作ることも出来ます。\n",
        "\n",
        "![pcbnewへネットリストをインポートした](https://github.com/hsgw/keyboard-made-by-python/blob/main/notebook/imgs/kicad_pcbnew.png?raw=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rtU9DM-AD2hu"
      },
      "source": [
        "# pcbflowで基板を設計する\n",
        "[pcbflow](https://github.com/michaelgale/pcbflow)を使って、skidlで設計したフットプリントとピンの接続情報を読み込み、基板上に実際に部品を配置し配線を繋いでいきます。随時、画像ファイルでプレビューをしたり、最終的には製造に必要なガーバーファイル一式を出力します。\n",
        "\n",
        "配線は昔ながらの[Turtle graphics](https://docs.python.org/ja/3/library/turtle.html)風の表記で行います。\n",
        "\n",
        "pcbflowは開発中(?)のようで意図しない動作や不具合があったため修正と追加をしました。今記事ではその[folk](https://github.com/hsgw/pcbflow/tree/fix_kicad)を使用します。\n",
        "\n",
        "- pcbflow https://github.com/michaelgale/pcbflow\n",
        "- pcvflowのドキュメントはREADME.mdにあります\n",
        "- folkして修正したpcbflow https://github.com/hsgw/pcbflow/tree/fix_kicad"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mXU5TZOeFBGI"
      },
      "source": [
        "## pcbflowのダウンロードとインストール\n",
        "以下のセルを実行してインストールします。\n",
        "インポートしてエラーがないことを確認します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "D4V_SArjWQXs",
        "outputId": "1d0507f1-bea5-4027-fa2c-08d44f90cbb0"
      },
      "outputs": [],
      "source": [
        "# インストールに時間がかかります\n",
        "!pip install git+https://github.com/hsgw/pcbflow/@fix_kicad"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zZH-ygq3FlJu"
      },
      "outputs": [],
      "source": [
        "from pcbflow import *"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "qkWfPZF2I37R"
      },
      "source": [
        "## 定数を宣言する\n",
        "ネットリストと同じようによく使う値を定数として宣言しておきます。   \n",
        "座標のY軸が反転していて違和感があったので変換するための関数も宣言しています。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "a031kS7yJEtJ"
      },
      "outputs": [],
      "source": [
        "BOARD_WIDTH = 76.0\n",
        "BOARD_HEIGHT = 57.0\n",
        "\n",
        "KEY_PITCH = 19.0\n",
        "\n",
        "SCREW_HOLE = 2.2\n",
        "\n",
        "LAYER_TOP = \"GTL\"\n",
        "LAYER_BOTTOM = \"GBL\"\n",
        "\n",
        "# Y軸の座標を反転させる関数\n",
        "def pos(x, y):\n",
        "  return (x, BOARD_HEIGHT - y)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "K2o9ziq3N2Bb"
      },
      "source": [
        "## 基板と回路の変数を宣言する\n",
        "基板と回路にアクセスするための変数を宣言しておきます。\n",
        "基板はpcbflowのもの、回路はskidlのものです。\n",
        "\n",
        "`board`の宣言時に外形の大きさを指定し、外形線を追加します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "8bzraF-yOaIH"
      },
      "outputs": [],
      "source": [
        "# skidlで作った回路情報\n",
        "circuit = builtins.default_circuit\n",
        "# pcbflowの基板\n",
        "board = Board((BOARD_WIDTH, BOARD_HEIGHT))\n",
        "board.add_outline()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9nWBUCJJNdgE"
      },
      "source": [
        "## デザインルールを設定する\n",
        "ドリルの大きさや配線の太さなど、基板のデザインルールを設定します。\n",
        "\n",
        "この設定を基本として基板を配線していきます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "PdPPua_3Nuoi"
      },
      "outputs": [],
      "source": [
        "board.drc.trace_width = 0.5        # 配線の太さ\n",
        "board.drc.via_drill = 0.6          # ビアのドリル径\n",
        "board.drc.via_annular_ring = 0.4   # ビアのパッド径\n",
        "board.drc.clearance = 0.4          # 配線のクリアランス"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NFHOh_4xaSMc"
      },
      "source": [
        "## 固定用の穴をあける\n",
        "スイッチの間に基板を固定するための穴を作ります。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1AY3bcKpaa6M"
      },
      "outputs": [],
      "source": [
        "board.add_hole(pos(KEY_PITCH * 2, KEY_PITCH), SCREW_HOLE)\n",
        "board.add_hole(pos(KEY_PITCH * 3, KEY_PITCH), SCREW_HOLE)\n",
        "board.add_hole(pos(KEY_PITCH * 3, KEY_PITCH * 2), SCREW_HOLE)\n",
        "board.add_hole(pos(KEY_PITCH, KEY_PITCH * 2), SCREW_HOLE)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "gxtMIg5IcLKb"
      },
      "source": [
        "### 画像ファイルを出力してプレビューしてみる\n",
        "ここで一度画像ファイルを出力して基板上に正しく穴があいているか確認してみます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "id": "22HqPC8IcsFh",
        "outputId": "3c5cb1e1-9994-4dbc-9208-43b08752bbcb"
      },
      "outputs": [],
      "source": [
        "from IPython.display import Image\n",
        "\n",
        "# 現在のboardをpngファイルとして書き出す\n",
        "board.save_png(\"kbd_python\", subdir=\"pcb_png\")\n",
        "\n",
        "# 表示\n",
        "Image(\"pcb_png/kbd_python_preview_all.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_top.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_bot.png\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "t2mUZjAfY31k"
      },
      "source": [
        "## 部品に位置情報を追加する\n",
        "skidlの部品それぞれに基板上の位置・回転・実装面の情報を追加していきます。\n",
        "\n",
        "今記事ではいきなり位置を指定していますが、基板に配置してはプレビューを繰り返しながら決めていきました。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "R4cUuHhZZPm_"
      },
      "outputs": [],
      "source": [
        "# sw, d, xiao, oledはskidlのところで宣言した変数です\n",
        "\n",
        "# スイッチとダイオードはブロックにまとめて位置を指定する\n",
        "# ブロックごとにスイッチとダイオードの位置関係は同じ\n",
        "for sw, d, mapping in zip(switches, diodes, MATRIX_MAP):\n",
        "  sw.pos = pos(\n",
        "    mapping[1] * KEY_PITCH + KEY_PITCH / 2,\n",
        "    mapping[0] * KEY_PITCH + KEY_PITCH / 2,\n",
        "  )\n",
        "  sw.side = \"top\"\n",
        "  sw.rotate = 0\n",
        "  d.pos = (sw.pos[0] + 6, sw.pos[1] + 5)\n",
        "  d.side = \"bottom\"\n",
        "  d.rotate = 0\n",
        "\n",
        "xiao.pos = pos(11.25, 11.5)\n",
        "xiao.side = \"bottom\"\n",
        "xiao.rotate = 0\n",
        "\n",
        "oled.pos = pos(9.5, 36)\n",
        "oled.side = \"top\"\n",
        "oled.rotate = 270"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jQy4fcGbfAfd"
      },
      "source": [
        "## 基板に部品を配置する\n",
        "追加した位置情報を元にskidlの部品をpcbflowの基板へ変換しながら配置していきます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "MBdn1GA4e_ea"
      },
      "outputs": [],
      "source": [
        "for part in circuit.parts:\n",
        "  try:\n",
        "    # skidlの部品を読み込んでpcbflowのboardに配置していく\n",
        "    SkiPart(board.DC(part.pos).right(part.rotate), part, side=part.side)\n",
        "  except AttributeError:\n",
        "    print(f\"{part.ref} has no pos or side\")\n",
        "    continue"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "376guVBxgOOm"
      },
      "source": [
        "### プレビュー"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "id": "pMku38a9gIMl",
        "outputId": "e09a38c7-d36a-4ed8-88b7-e3031accccbb"
      },
      "outputs": [],
      "source": [
        "from IPython.display import Image\n",
        "\n",
        "# 現在のboardをpngファイルとして書き出す\n",
        "board.save_png(\"kbd_python\", subdir=\"pcb_png\")\n",
        "\n",
        "# 表示\n",
        "Image(\"pcb_png/kbd_python_preview_all.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_top.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_bot.png\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HIvuWJ6_sxcM"
      },
      "source": [
        "## 配線：xiao-スイッチ(ROW)\n",
        "xiaoからスイッチのROWへの配線をします。   \n",
        "Turtle記法を使ってxiaoから配線を引き出してスイッチの`Pad`に接続します。\n",
        "\n",
        "skidlの`Pin`とpcbflowの`pads`は同じもの、部品のピンを指しています。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "QYuy9Np7sx9M",
        "outputId": "ed24ff82-e77b-46f8-fba6-2d8cdfdee9fe"
      },
      "outputs": [],
      "source": [
        "xiaoRef = \"U1\"\n",
        "\n",
        "# skidlのNetから名前で検索してPadの番号を返す関数\n",
        "# skidlのPin番号は1から、pcbflowのPad番号は0から始まる\n",
        "def get_pin_number_from_net(netLabel, ref):\n",
        "    net = Net.get(netLabel)\n",
        "    return list((int(x.num) - 1 for x in net.pins if x.ref == ref))[0]\n",
        "\n",
        "# skidlのNetから名前で検索して接続されているxiaoのPadを返す関数\n",
        "# kicadライブラリの都合でPad番号がずれているのを修正して返す\n",
        "# 最後のnewpath()は初期値に変な値が入っているのを初期化している\n",
        "def xiao_pads(net: str):\n",
        "  return (\n",
        "    board.get_part(xiaoRef)\n",
        "    .pads[get_pin_number_from_net(net, xiaoRef) + 14]\n",
        "    .newpath()\n",
        "  )\n",
        "\n",
        "# Net:ROWに繋がったxiaoとスイッチのピンを繋ぐ\n",
        "# ROWに繋がったxiaoのPad→レイヤーを指定→Turtle記法で配線→SWのパッドまで軸を合わせて配線\n",
        "xiao_pads(\"ROW0\").set_layer(LAYER_TOP).w(\"r 45\").align_meet(\n",
        "  board.get_part(\"SW1\").pads[0], \"x\"\n",
        ")\n",
        "xiao_pads(\"ROW1\").set_layer(LAYER_TOP).w(\"r 45\").align_meet(\n",
        "  board.get_part(\"SW4\").pads[0], \"y\"\n",
        ")\n",
        "xiao_pads(\"ROW2\").set_layer(LAYER_TOP).w(\"l 180 f 12 r 45\").align_meet(\n",
        "  board.get_part(\"SW8\").pads[0], \"y\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fSKc2qDnu_c3"
      },
      "source": [
        "### プレビュー"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 965
        },
        "id": "bA690BhQu_c8",
        "outputId": "dae75eb2-5ee9-47a2-8a80-598494b4f07e"
      },
      "outputs": [],
      "source": [
        "from IPython.display import Image\n",
        "\n",
        "# 現在のboardをpngファイルとして書き出す\n",
        "board.save_png(\"kbd_python\", subdir=\"pcb_png\")\n",
        "\n",
        "# 表示\n",
        "Image(\"pcb_png/kbd_python_preview_all.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_top.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_bot.png\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7g0j_Biziy3k"
      },
      "source": [
        "## 配線：ダイオード-スイッチ・スイッチ-スイッチ(ROW)・ダイオード-ダイオード(COL)\n",
        "接続されているピンの位置関係が同じものであれば同じように接続できるので、ループを回して配線します。\n",
        "\n",
        "COLの配線では途中でビアを作ります。ビアの情報を保存しておいて後ほどxiaoと接続します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "tfu1rTPnjlSW"
      },
      "outputs": [],
      "source": [
        "# スイッチとダイオードはブロックとして同じように並べたので同じように配線できる\n",
        "# REF Noでアクセスしたいので1始まり\n",
        "for i in range(1, KEY_COUNT + 1):\n",
        "  sw = board.get_part(f\"SW{i}\")\n",
        "  d = board.get_part(f\"D{i}\")\n",
        "  # 配線をはじめるpadを指定→レイヤーを指定→Turtle記法で配線→パッドと軸を合わせて接続\n",
        "  d.pads[0].set_layer(LAYER_BOTTOM).w(\"f 1 r 45\").align_meet(sw.pads[1], \"x\")\n",
        "\n",
        "# スイッチマトリクスのROWに繋がっているスイッチのPinは直線で並んでいるので同じように配線できる\n",
        "# ROWによってスイッチの個数が違うので注意\n",
        "for i in range(3):\n",
        "  # ROWのNet情報からSWに繋がっているもののRef Noを配列にする\n",
        "  refs = list(x.ref for x in netRows[i].pins if x.ref.startswith(\"SW\"))\n",
        "  for j in range(len(refs) - 1):\n",
        "    # 配線を始めるpadを指定→新しいパスを作る→レイヤーの指定→Turtle記法で配線→指定したpadへ直線で接続\n",
        "    board.get_part(refs[j]).pads[0].newpath().set_layer(LAYER_TOP).w(\n",
        "      \"r 90 f 2 l 45 f 1 r 45 f 2.5 r 45 f1\"\n",
        "    ).meet(board.get_part(refs[j + 1]).pads[0])\n",
        "\n",
        "# ビアの情報を保存しておく配列\n",
        "# xiaoと接続するときに使う\n",
        "viaCol = [0] * 4\n",
        "\n",
        "# スイッチマトリクスのCOL1-3に繋がるダイオードは直線に並んでいるので同じように配線できる\n",
        "# 途中でビアを挟む\n",
        "for i in range(1, 4):\n",
        "  d1 = board.get_part(f\"D{i}\")\n",
        "  d2 = board.get_part(f\"D{i+3}\")\n",
        "  d3 = board.get_part(f\"D{i+7}\")\n",
        "  # ビアの情報を保持しておくためにダイオード-ビアを先に配線する\n",
        "  via = (\n",
        "    d1.pads[1]\n",
        "    .set_layer(LAYER_BOTTOM)\n",
        "    .w(\"l 180 f 1.25 r 45 f 2 l 45 f 6.5 l 45\")\n",
        "    .align(d2.pads[1], \"y\")\n",
        "    .wire()\n",
        "    .via()\n",
        "  )\n",
        "  viaCol[i] = via\n",
        "  # ビアから次のダイオードのPadまで配線する\n",
        "  via.set_layer(LAYER_BOTTOM).meet(d2.pads[1])\n",
        "  d2.pads[1].set_layer(LAYER_BOTTOM).w(\n",
        "    \"l 180 f 1 r 45 f 2 l 45 f 6.5 l 45\"\n",
        "  ).align_meet(d3.pads[1], \"y\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RHUfHju2lnf4"
      },
      "source": [
        "### プレビュー"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 965
        },
        "id": "TdpxBEHelnf9",
        "outputId": "4505fad8-49ab-4c5d-b885-1b856bfea16c"
      },
      "outputs": [],
      "source": [
        "from IPython.display import Image\n",
        "\n",
        "# 現在のboardをpngファイルとして書き出す\n",
        "board.save_png(\"kbd_python\", subdir=\"pcb_png\")\n",
        "\n",
        "# 表示\n",
        "Image(\"pcb_png/kbd_python_preview_all.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_top.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_bot.png\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4sNQELnWwx5h"
      },
      "source": [
        "## 配線：ビア-xiao(COL)\n",
        "保存しておいたビアからxiaoへ配線します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "uQ1xlQk8wx5m",
        "outputId": "66bcf5b0-6c5b-4774-fff5-11b4a61d82e8"
      },
      "outputs": [],
      "source": [
        "# ビアからxiaoのpadへ配線する\n",
        "viaCol[1].set_layer(LAYER_TOP).w(\"l 45 f 11 l 45\").align_meet(\n",
        "  xiao_pads(\"COL1\"), \"x\"\n",
        ")\n",
        "viaCol[2].set_layer(LAYER_TOP).w(\"f 2 l 45 f 29.5 l 45\").align_meet(\n",
        "  xiao_pads(\"COL2\"), \"x\"\n",
        ")\n",
        "viaCol[3].set_layer(LAYER_TOP).w(\"f 4 l 45 f 48.5 l 45\").align_meet(\n",
        "  xiao_pads(\"COL3\"), \"x\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BZp2rf73xjPt"
      },
      "source": [
        "### プレビュー"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 965
        },
        "id": "-eCOnReyxjPt",
        "outputId": "ab969902-942c-4924-c529-4064fd769043"
      },
      "outputs": [],
      "source": [
        "from IPython.display import Image\n",
        "\n",
        "# 現在のboardをpngファイルとして書き出す\n",
        "board.save_png(\"kbd_python\", subdir=\"pcb_png\")\n",
        "\n",
        "# 表示\n",
        "Image(\"pcb_png/kbd_python_preview_all.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_top.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_bot.png\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ViFc_c0oxjPo"
      },
      "source": [
        "## 配線：xiao-OLED\n",
        "xiaoからOLEDへ配線します。\n",
        "\n",
        "Netの名前を列挙しておいてskidlのNet情報から接続するPadの番号を調べます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "pEkSdb7YxjPs"
      },
      "outputs": [],
      "source": [
        "oledRef = \"DISP1\"\n",
        "\n",
        "# ビアからxiaoのpadへ配線する\n",
        "for net in [\"GND\", \"3.3V\", \"SCL\", \"SDA\"]:\n",
        "  oledPin = board.get_part(oledRef).pads[get_pin_number_from_net(net, oledRef)]\n",
        "  xiao_pads(net).set_layer(LAYER_TOP).left(135).align_meet(oledPin, \"y\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Xa0VwbDJwx5m"
      },
      "source": [
        "## 配線の完成\n",
        "これで全ての配線が終わりました！"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 965
        },
        "id": "_z-12LbDwx5m",
        "outputId": "31258366-12f5-4a81-fa95-ff16facfab80"
      },
      "outputs": [],
      "source": [
        "from IPython.display import Image\n",
        "\n",
        "# 現在のboardをpngファイルとして書き出す\n",
        "board.save_png(\"kbd_python\", subdir=\"pcb_png\")\n",
        "\n",
        "# 表示\n",
        "Image(\"pcb_png/kbd_python_preview_all.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_top.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_bot.png\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "KduW_1h4OLdU"
      },
      "source": [
        "## 基板上に絵を配置する\n",
        "基板のシルクレイヤーにロゴなどの画像を配置します。\n",
        "使用する画像データは冒頭のリソース内にあります。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Ygvq-EqzOxDH"
      },
      "outputs": [],
      "source": [
        "DIR_IMGS = \"keyboard-made-by-python/\"\n",
        "\n",
        "board.add_bitmap(\n",
        "  (47.5, 28.5), \n",
        "  DIR_IMGS + \"hardware/imgs/python-logo.png\",\n",
        "  side=\"top\",\n",
        "  scale=0.85\n",
        ")\n",
        "\n",
        "board.add_bitmap(\n",
        "  (11.25, 45),\n",
        "  DIR_IMGS + \"hardware/imgs/QR.png\",\n",
        "  side=\"bottom\",\n",
        "  scale=0.75,\n",
        ")\n",
        "\n",
        "board.add_bitmap(\n",
        "  (11, 28.5),\n",
        "  DIR_IMGS + \"hardware/imgs/logo-python-powered-w-logo.png\",\n",
        "  side=\"bottom\",\n",
        "  layer=\"GBS\",\n",
        "  scale=0.7,\n",
        ")\n",
        "board.add_bitmap(\n",
        "  (11, 28.5),\n",
        "  DIR_IMGS + \"hardware/imgs/logo-python-powered-w-logoBG.png\",\n",
        "  side=\"bottom\",\n",
        "  layer=\"GBL\",\n",
        "  scale=0.7,\n",
        ")\n",
        "board.add_bitmap(\n",
        "  (11, 28.5),\n",
        "  DIR_IMGS + \"hardware/imgs/logo-python-powered-w-text.png\",\n",
        "  side=\"bottom\",\n",
        "  scale=0.7,\n",
        ")\n",
        "\n",
        "board.add_bitmap(\n",
        "  (47.5, 22),\n",
        "  DIR_IMGS + \"hardware/imgs/dm9-logo.png\",\n",
        "  side=\"bottom\",\n",
        "  scale=0.8,\n",
        ")\n",
        "\n",
        "board.add_bitmap(\n",
        "  (66.5, 22),\n",
        "  DIR_IMGS + \"hardware/imgs/hsgw-logo.png\",\n",
        "  side=\"bottom\",\n",
        "  scale=0.4,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "32qY1ujHQRuA"
      },
      "source": [
        "### プレビュー"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "id": "x6OEVJKUQO1W",
        "outputId": "01b64bbd-db89-4d8a-f7b6-c1349efc29d4"
      },
      "outputs": [],
      "source": [
        "from IPython.display import Image\n",
        "\n",
        "# 現在のboardをpngファイルとして書き出す\n",
        "board.save_png(\"kbd_python\", subdir=\"pcb_png\")\n",
        "\n",
        "# 表示\n",
        "Image(\"pcb_png/kbd_python_preview_all.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_top.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_bot.png\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HQ_stk2iQmiy"
      },
      "source": [
        "## 基板上にテキストを配置する\n",
        "基板のシルクレイヤーにテキストを配置します。\n",
        "\n",
        "JLCPCBに発注するので注文番号の入る位置を指定しておきます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1Lf-MAn1Qmiy"
      },
      "outputs": [],
      "source": [
        "board.add_text(\n",
        "  (47.5, 41.5),\n",
        "  \"https://github.com/hsgw/keyboard_made_by_python\",\n",
        "  scale=1.25,\n",
        "  side=\"bottom\",\n",
        ")\n",
        "\n",
        "board.add_text(\n",
        "  (BOARD_WIDTH - 39, 5),\n",
        "  \"KEYBOARD MADE BY PYTHON\",\n",
        "  scale=2,\n",
        "  side=\"bottom\",\n",
        "  justify=\"left\",\n",
        ")\n",
        "board.add_text(\n",
        "  (BOARD_WIDTH - 44, 4.75),\n",
        "  \"Rev.1\",\n",
        "  scale=1.25,\n",
        "  side=\"bottom\",\n",
        "  justify=\"left\",\n",
        ")\n",
        "board.add_text(\n",
        "  (BOARD_WIDTH - 47.5, 2.25),\n",
        "  \"(c) 2022, Takuya Urakawa / Dm9Records / 5z6p.com\",\n",
        "  scale=1.25,\n",
        "  side=\"bottom\",\n",
        "  justify=\"left\",\n",
        ")\n",
        "\n",
        "# MAGIC WORD for JLCPCB\n",
        "board.add_text(\n",
        "  (11.5, 55),\n",
        "  \"JLCJLCJLCJLC\",\n",
        "  scale=1.1,\n",
        "  side=\"bottom\",\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "3FdBYSKrQmiz"
      },
      "source": [
        "### プレビュー"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "id": "8lkcYwWVQmiz",
        "outputId": "3c3bb898-f533-4105-f32b-46901d75f7fc"
      },
      "outputs": [],
      "source": [
        "from IPython.display import Image\n",
        "\n",
        "# 現在のboardをpngファイルとして書き出す\n",
        "board.save_png(\"kbd_python\", subdir=\"pcb_png\")\n",
        "\n",
        "# 表示\n",
        "Image(\"pcb_png/kbd_python_preview_all.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_top.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_bot.png\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dvHvS6UHRLil"
      },
      "source": [
        "# 基板の完成とガーバーファイルの出力\n",
        "最後に製造用ガーバーファイルを出力して完成です。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "KIt69DLaRIgA",
        "outputId": "6d7c135a-49d8-4036-d2b3-84f697309b49"
      },
      "outputs": [],
      "source": [
        "board.save_gerbers(\"kbd_python\", subdir=\"pcb_gerber\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "TjFdKPM9Sont"
      },
      "source": [
        "ガーバーファイルの確認にはkicadのガーバービュアーを使用しました。\n",
        "\n",
        "![kicadのガーバービュアー](../imgs/kicad_gerberviewer.png)\n",
        "\n",
        "また、kicadのガーバービュアーからpcbnewのデータへ変換できます。発注時には一度pcbnewへ読み込んでシルクのデータを整えたガーバーファイルを再度出力しました。\n",
        "\n",
        "![pcbnewへ変換した基板](../imgs/kicad_gerber_to_pcbnew.png)"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# 基板を発注する\n",
        "\n",
        "![届いた基板](../imgs/pcb.jpg)\n",
        "\n",
        "ガーバーファイルをまとめてJLCPCBに発注しました。5枚で$8程度、2週間弱で到着しました。\n",
        "\n",
        "# 部品のはんだづけ\n",
        "\n",
        "![はんだづけした基板](../imgs/soldered_pcb_top.jpg)\n",
        "![はんだづけした基板](../imgs/soldered_pcb_bottom.jpg)\n",
        "\n",
        "用意しておいた部品をはんだづけしました。これで基板の設計工程は完成です。"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "include_colab_link": true,
      "provenance": []
    },
    "kernelspec": {
      "display_name": ".venv",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.8.10 (default, Jun 22 2022, 20:18:18) \n[GCC 9.4.0]"
    },
    "vscode": {
      "interpreter": {
        "hash": "b68c839e49670d3aa77ea8bdd6f541fdcce81585b85d6e1d97674ad5f32db92f"
      }
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
